/**
 * Assignment 2             P2.java                Due: July 6, 2011 @ 6:00 am
 *
 * login: cs11ubn
 */

import java.util.*;

/**
 * Class P2
 * Displays the monthly calendar requested, correctly offsetting for leap years.
 * Takes input in the format <year> <month>
 * Earliest it can go is JAN 1990
 */
public class P2
{
  // Makes it easier to read if the months are held in constants
  private static final int JAN = 1; // January
  private static final int FEB = 2;
  private static final int MAR = 3;
  private static final int APR = 4;
  private static final int MAY = 5;
  private static final int JUN = 6;
  private static final int JUL = 7;
  private static final int AUG = 8;
  private static final int SEP = 9;
  private static final int OCT = 10;
  private static final int NOV = 11;
  private static final int DEC = 12;
    
  // General constants
  private static final int LEAP_FREQ = 4; // The frequency leap years occur
  private static final int LINE_WIDTH = 45; // The line width for each entry
  
  // Known values to base calendar off of
  private static final int LEAP = 2012; // Given known leap year
  // The absolute lowest it can go. Looked the year 1990 up and got these values
  private static final int CAL_BEGIN_YR = 1990;
  private static final int CAL_BEGIN_OFFSET = 1; // Number of offset that year
  
  /**
   * Prints to console the formatted month name as well as the days of the week
   * columns
   */
  public static void prtMonthTitle(int month)
  {
    String monthName; // Holds the name of the month
    StringBuffer buf = new StringBuffer(); // Better way to construct the stars
    int numStars; // Holds the number of stars required to center

    // Get the month name
    switch (month)
    {
      case JAN: // JAN
        monthName = "January";
        break;
      case FEB: // FEB
        monthName = "February";
        break;
      case MAR: // MAR
        monthName = "March";
        break;
      case APR: // APR
        monthName = "April";
        break;
      case MAY: // MAY
        monthName = "May";
        break;
      case JUN: // JUN
        monthName = "June";
        break;
      case JUL: // JUL
        monthName = "July";
        break;
      case AUG: // AUG
        monthName = "August";
        break;
      case SEP: // SEP
        monthName = "September";
        break;
      case OCT: // OCT
        monthName = "October";
        break;
      case NOV: // NOV
        monthName = "November";
        break;
      case DEC: // DEC
        monthName = "December";
        break;
      default:
        monthName = "ERROR";
        break;
    }
    
    // Get the number of stars required based on the line width and the length
    // of the month name
    numStars = (LINE_WIDTH - monthName.length());
    
    // Add the first set of stars
    for(int i = 0; i < numStars/2; i++)
    {
      buf.append("*");
    }
    
    // Add the month name
    buf.append(monthName);
    
    // Add the last set of stars
    for(int i = 0; i < numStars/2; i++)
    {
      buf.append("*");
    }
    
    // If the number of stars required was odd, add the missing star
    if (numStars % 2 != 0)
    {
      buf.append("*");
    }
    
    // Write the stars and month to the console
    System.out.println(buf.toString());
    
    // Write the days of the week columns
    System.out.println("SUN    MON    TUE    WED    THU    FRI    SAT");
  }
  
  /**
   * Returns the number of days in a given month. Adjusts for leap year
   */
  public static int getNumDaysInMonth(int month, boolean isLeapYr)
  {
    int numDays = 0; // The number of days in that month
    
    // Switch based on the given month
    switch(month)
    {
      case JAN:
      case MAR:
      case MAY:
      case JUL:
      case AUG:
      case OCT:
      case DEC:
        numDays = 31; // All of the above have 31 days, assign it
        break;
      case APR:
      case JUN:
      case SEP:
      case NOV:
        numDays = 30; // All of the above have 30 days, assign it
        break;
      case FEB:
        // Check if it is a leap year or not, if so then feb has 29 days
        if (isLeapYr)
          numDays = 29; // Leap year, has 29 days, assign
        else
          numDays = 28; // Not a leap year, has 28 days, assign
        break;
      default: // Shouldn't get here
        numDays = 0; // Wanted to give default case some love
        break;
    }


    return numDays; // Return it
  }
  
  public static void main(String[] args)
  {
    // Constants
    final int THIS_YR = 2011; // Holds the current year
    final int MON = 1; // Monday
    final int SAT = 7; // Saturday
    final int DAYS_WK = 7; // Days per week
    final int MIN_YR = 2010; // Year range min
    final int MAX_YR = 2020; // Year range max
    
    boolean isLeap = false; // True if leap year, false if not
    char choice; // The users choice to continue or not
    int mon; // The desired month
    int year; // The desired year
    int days = 0; // The number of days in the month
    int weekOffset = 0; // The offset for the month
    
    Scanner scan = new Scanner(System.in);
    String inputStr = null;
    
    do
    {
      System.out.println();
    
      do
      {
        // Prompt for input
        System.out.print("Enter year (" + MIN_YR + "-" + MAX_YR + ")");
        System.out.print(" and month (1-12) to display calendar:  ");
        year = scan.nextInt(); // Get the desired year (First entry)
        mon = scan.nextInt(); // Get the desired month (Second entry)
        
        // Error check
        if ((year < MIN_YR) || (year > MAX_YR)) // Check if in range
        {
          // Prompt error
          System.out.print("ERROR - Enter a year in range (");
          System.out.println(MIN_YR + "-" + MAX_YR + ")!");
        }
        if ((mon < JAN) || (mon > DEC)) // Check if in range
        {
          // Prompt error
          System.out.print("ERROR - Enter a month in range of (");
          System.out.println(JAN + "-" + DEC + ")!");
        }
        
        // Loop while any of the values are out of range
      } while((year < MIN_YR) || (year > MAX_YR) || (mon < JAN) || (mon > DEC));
      
      // Print year header
      System.out.print("********************" + year);
      System.out.println("*********************");
      
      // Print month title and days header
      prtMonthTitle(mon);
      
      weekOffset = CAL_BEGIN_OFFSET;
      
      
      // Start off with getting the offset up to this year, but not including
      for (int yrLoop = CAL_BEGIN_YR; yrLoop < year; yrLoop++)
      {
        isLeap = false; // Reset the isLeap variable
                    
        // Find out if it is a leap year, using this it wont matter if the year
        // it is currently using is below the given constant leap year.
        if (yrLoop >= LEAP) // If the yrLoop is bigger than the given leap year
        {
          // Loop through all combinations of 4*i + the given leap year, see if
          // any match our year
          for (int i = 0; ((LEAP_FREQ * i) + LEAP) <= yrLoop; i++)
          {
            if (((LEAP_FREQ * i) + LEAP) == yrLoop) // Check if the year is leap
            {
              isLeap = true; // Assign the variable
            }
          }
        }
        else // If the yrLoop is less than the given leap year
        {
          // Loop through all combinations of the given leap year - (4*i), see if
          // any match our year
          for (int i = 0; (LEAP - (LEAP_FREQ * i)) >= yrLoop; i++)
          {
            // Check
            if ((LEAP - (LEAP_FREQ * i)) == yrLoop)
            {
              isLeap = true; // Assign the var
            }
          }
        }
        
        // Loop through the months now that leap or not is determined.
        for (int i = JAN; i <= DEC; i++)
        {
          // Get the number of days in that month
          days = getNumDaysInMonth(i,isLeap);
          
          // Get the last day of the week and add it to the weekOffset
          weekOffset += days % DAYS_WK;
          
          // Check if the weekOffset should move down to a new week
          if (weekOffset >= DAYS_WK)
          {
            weekOffset -= DAYS_WK; // If true remove a weeks worth of offset
          }
        }
      }
      
      // Find if desired year is leap or not
      if (year >= LEAP)
      {
        // Same checks as above
        for (int i = 0; ((LEAP_FREQ * i) + LEAP) <= year; i++)
        {
          if (((LEAP_FREQ * i) + LEAP) == year)
          {
            isLeap = true;
          }
        }
      }
      else
      {
        // Same as above
        for (int i = 0; (LEAP - (LEAP_FREQ * i)) >= year; i++)
        {
          if ((LEAP - (LEAP_FREQ * i)) == year)
          {
            isLeap = true;
          }
        }
      }
      
      // Loop through the months including the desired month
      for (int i = JAN; i <= mon; i++)
      {
        days = getNumDaysInMonth(i,isLeap); // Get the days of the month
        
        // Only add to the offset if not the desired month
        if (i != mon)
          weekOffset += days % DAYS_WK; // Get the last day of the week
        
        // Remove a weeks worth of offset if required
        if (weekOffset >= DAYS_WK)
        {
          weekOffset -= DAYS_WK;
        }
      }
      
      // Debug, please ignore
      //System.out.println(weekOffset);
      
      // Insert the correct amount of empty chunks based on the offset
      for (int i = 0; i < weekOffset; i++)
      {
        System.out.print("       ");
      }
      
      // Print the days of the week
      for (int i = 1; i <= days; i++)
      {
        System.out.printf("%2d     ",i); // Format the days
        
        // If the week has advanced, go to the new line
        // Example (January 2011):
        //     weekOffset = 6, i = 1
        // it prints the day above, then (1+6)%7 == 0 = true, new line
        // for the next sat, i = 8. (8+6)%7 == 0 = true, new line, etc.
        if ((i + weekOffset) % (DAYS_WK) == 0)
            System.out.println();
      }
      
      System.out.println(); // Put the cursor on the next line
      
      // Prompt for continue
      System.out.print("Want to display another month (y/n)?  ");
      inputStr = scan.next(); // Get input
      choice = inputStr.charAt(0); // Get the choice char
    } while ((choice != 'n') && (choice != 'N')); // Loop while not n nor N
    
    scan.close(); // Close the scanner
    System.exit(0); // Close all connections
  }
}
